
#include <BMap>
#include <BRender>
#include <BOpenGL>

#include <BLine>
#include <BLineStripe>
#include "member_BLineStripe.h"
#include "member_BShape.h"

using namespace BWE;

#define member					(*(member_BLineStripe*)_ptr)
#define member_allocate()		_ptr = new member_BLineStripe(this)
#define member_release()		delete (member_BLineStripe*)_ptr

BLineStripe::BLineStripe()
{
	member_allocate();
}
BLineStripe::~BLineStripe()
{
	member_release();
}

void BLineStripe::copy(const BShape* other)
{
	BShape::copy(other);
	if (const BLineStripe* stripe = dynamic_cast<const BLineStripe*>(other))
	{
		member.thickness = stripe_member(stripe)->thickness;
		member.vertices = stripe_member(stripe)->vertices;
		member.length = stripe_member(stripe)->length;
		member.dirty = stripe_member(stripe)->dirty;
	}
}
BShape* BLineStripe::clone() const
{
	BLineStripe* stripe = new BLineStripe();
	stripe->copy(this);
	return stripe;
}

void BLineStripe::setVertices(BVectorArray* vertices)
{
	if (member.vertices != vertices)
	{
		member.vertices = vertices;
		member.cornerArcs.resize(vertices->size());
		member.lineLengths.resize(vertices->size());
		this->dirty();
	}
}
const BVectorArray* BLineStripe::vertices() const
{
	return member.vertices;
}
BVectorArray* BLineStripe::vertices()
{
	return member.vertices;
}

void BLineStripe::setCloseLoop(bool closeLoop)
{
	if (member.closeLoop != closeLoop)
	{
		member.closeLoop = closeLoop;
		this->dirty();
	}
}
bool BLineStripe::closeLoop() const
{
	return member.closeLoop;
}

void BLineStripe::setRadius(BReal radius)
{
	if(member.radius != radius)
	{
		member.radius = radius;
		this->dirty();
	}
}
BReal BLineStripe::radius() const
{
	return member.radius;
}

void BLineStripe::setRadius(int index, BReal radius)
{
	if (!member.vertices->check(index))
		return;
	member.cornerArcs.resize(member.vertices->size());
	if (member.cornerArcs[index].radius != radius)
	{
		member.cornerArcs[index].radius = radius;
		this->dirty();
	}
}
BReal BLineStripe::radius(int index) const
{
	member.cornerArcs.resize(member.vertices->size());
	return member.cornerArcs(index).radius;
}

void BLineStripe::setThickness(BReal thickness)
{
	if (member.thickness != thickness)
	{
		member.thickness = thickness;
		this->dirty();
	}
}
BReal BLineStripe::thickness() const
{
	return member.thickness;
}

void BLineStripe::setCornerVisible(bool cornerVisible)
{
	if (member.cornerVisible != cornerVisible)
	{
		member.cornerVisible = cornerVisible;
		this->fresh();
	}
}
bool BLineStripe::cornerVisible() const
{
	return member.cornerVisible;
}

const BVector& BLineStripe::head() const
{
	return member.vertices->first();
}
const BVector& BLineStripe::tail() const
{
	return member.vertices->last();
}

BLine BLineStripe::line(int index) const
{
	if (index >= 0 && index < member.vertices->size() - 1)
	{
		const BVector& v0 = member.vertices->at(index);
		const BVector& v1 = member.vertices->at(index + 1);
		return BLine(v0, v1);
	}
	if (member.closeLoop)
	{
		const BVector& v0 = member.vertices->last();
		const BVector& v1 = member.vertices->first();
		return BLine(v0, v1);
	}
	if (index < 0)
	{
		const BVector& first = member.vertices->first();
		return BLine(first, first);
	}
	const BVector& last = member.vertices->last();
	return BLine(last, last);
}
BLine BLineStripe::lineOf(BReal value) const
{
	if (!member.vertices || member.vertices->size() < 2)
		return BLine();
	if (value < 0)
	{
		const BVector& v0 = member.vertices->at(0);
		const BVector& v1 = member.vertices->at(1);
		return BLine(v0, v1);
	}
	for (int i = 1; i < member.vertices->size(); i++)
	{
		const BVector& v0 = member.vertices->at(i - 1);
		const BVector& v1 = member.vertices->at(i);
		BReal len = (v0 - v1).length();
		if (value > len)
		{
			value -= len;
			continue;
		}
		return BLine(v0, v1);
	}
	const BVector& v0 = member.vertices->at(member.vertices->size() - 2);
	const BVector& v1 = member.vertices->at(member.vertices->size() - 1);
	return BLine(v0, v1);

}

BReal BLineStripe::closest(const BVector& point) const
{
	if (member.vertices->empty())
		return 0;
	BReal minDist = REAL_MAX;
	BReal value = 0;
	BReal sumValue = 0;
	int count = member.vertices->size();
	for (int i = 0; i < count; i++)
	{
		if (!member.closeLoop && i == count - 1)
			break;
		int nx = (i == count - 1) ? 0 : (i + 1);
		BVector head = member.vertices->at(i);
		BVector tail = member.vertices->at(nx);
		CornerArc& corner = member.cornerArcs[i];
		if (corner.vertices.size())
		{
			BVector closestVertex;
			BVector closestDirect;
			BReal closestValue;
			if (corner.closest(point, closestVertex, closestDirect, closestValue))
			{
				BReal dist = (closestVertex - point).length2();
				if (dist < minDist)
				{
					minDist = dist;
					value = sumValue + closestValue;
				}
			}
			head = corner.vertices.last();
		}
		CornerArc& nextCorner = member.cornerArcs[nx];
		if (nextCorner.vertices.size())
		{
			tail = nextCorner.vertices.first();
		}
		BLine line(head, tail);
		BVector closestVertex = line.closest(point, false);
		BReal dist = (closestVertex - point).length2();
		if (dist < minDist)
		{
			minDist = dist;
			value = sumValue + corner.length + line.direct().dot(point - head);
		}
		sumValue += member.lineLengths[i];
	}
	if (member.closeLoop && value < 0)
	{
		value += member.length;
	}
	return value;
}
BReal BLineStripe::closest(const BVector& point, BVector& vertex, BVector& direct) const
{
	if (member.vertices->empty())
		return 0;
	vertex = member.vertices->first();
	BReal minDist = REAL_MAX;
	BReal value = 0;
	BReal sumValue = 0;
	int count = member.vertices->size();
	for (int i = 0; i < count; i++)
	{
		if (!member.closeLoop && i == count - 1)
			break;
		int nx = (i == count - 1) ? 0 : (i + 1);
		BVector head = member.vertices->at(i);
		BVector tail = member.vertices->at(nx);
		CornerArc& corner = member.cornerArcs[i];
		if (corner.vertices.size())
		{
			BVector closestVertex;
			BVector closestDirect;
			BReal closestValue;
			if (corner.closest(point, closestVertex, closestDirect, closestValue))
			{
				BReal dist = (closestVertex - point).length2();
				if (dist < minDist)
				{
					minDist = dist;
					vertex = closestVertex;
					direct = closestDirect;
					value = sumValue + closestValue;
				}
			}
			head = corner.vertices.last();
		}
		CornerArc& nextCorner = member.cornerArcs[nx];
		if (nextCorner.vertices.size())
		{
			tail = nextCorner.vertices.first();
		}
		BLine line(head, tail);
		BVector closestVertex = line.closest(point, false);
		BReal dist = (closestVertex - point).length2();
		if (dist < minDist)
		{
			minDist = dist;
			vertex = closestVertex;
			direct = line.direct();
			value = sumValue + corner.length + line.direct().dot(point - head);
		}
		sumValue += member.lineLengths[i];
	}
	if (member.closeLoop && value < 0)
	{
		value += member.length;
	}
	return value;
}

int BLineStripe::poseOf(BReal value, BVector& vertex, BVector& direct) const
{
	if (member.vertices.empty())
		return -1;
	BReal sumValue = 0;
	int count = member.vertices->size();
	for (int i = 0; i < count; i++)
	{
		int next = i + 1;
		if (i == count - 1)
		{
			if (!member.closeLoop)
				break;
			next = 0;
		}
		BReal length = member.lineLengths[i];
		BReal min = sumValue;
		sumValue += length;
		if (value <= sumValue)
		{
			direct = member.vertices->at(next) - member.vertices->at(i);
			direct.normalize();
			CornerArc& head = member.cornerArcs[i];
			if (head.vertices.size())
			{
				if (value - min < head.length)
				{
					BReal angle = head.halfAngle - head.halfAngle * (value - min) / head.length;
					BQuater quater(head.axis, -angle);
					BVector vec = head.vertices.last() - head.center;
					vertex = head.center + quater * vec;
					direct *= quater;
					return i;
				}
			}
			CornerArc& tail = member.cornerArcs[next];
			if (tail.vertices.size())
			{
				BReal end = sumValue - tail.length;
				if (value > end)
				{
					BReal angle = tail.halfAngle * (value - end) / tail.length;
					BVector vec = tail.vertices.first() - tail.center;
					BQuater quater(tail.axis, angle);
					vertex = tail.center + quater * vec;
					direct *= quater;
					return i;
				}
			}
			vertex = member.vertices->at(i) + direct * (value - min);
			return i;
		}
	}
	return -1;
}
int BLineStripe::poseOf(BReal value, BReal& ratio, BVector& vertex, BVector& direct) const
{
	ratio = 0;
	if (member.vertices.empty())
		return -1;
	BReal sumValue = 0;
	int count = member.vertices->size();
	for (int i = 0; i < count; i++)
	{
		int next = i + 1;
		if (i == count - 1)
		{
			if (!member.closeLoop)
				break;
			next = 0;
		}
		BReal length = member.lineLengths[i];
		BReal min = sumValue;
		sumValue += length;
		if (value <= sumValue)
		{
			direct = member.vertices->at(next) - member.vertices->at(i);
			direct.normalize();
			CornerArc& head = member.cornerArcs[i];
			if (head.vertices.size())
			{
				if (value - min < head.length)
				{
					ratio = (value - min) / head.length;
					BReal angle = head.halfAngle - head.halfAngle * (value - min) / head.length;
					BQuater quater(head.axis, -angle);
					BVector vec = head.vertices.last() - head.center;
					vertex = head.center + quater * vec;
					direct *= quater;
					return i;
				}
			}
			CornerArc& tail = member.cornerArcs[next];
			if (tail.vertices.size())
			{
				BReal end = sumValue - tail.length;
				if (value > end)
				{
					ratio = (value - end) / tail.length;
					BReal angle = tail.halfAngle * (value - end) / tail.length;
					BVector vec = tail.vertices.first() - tail.center;
					BQuater quater(tail.axis, angle);
					vertex = tail.center + quater * vec;
					direct *= quater;
					return i;
				}
			}
			ratio = (value - min) / tail.length;
			vertex = member.vertices->at(i) + direct * (value - min);
			return i;
		}
	}
	return -1;
}
BVector BLineStripe::vertexOf(BReal value) const
{
	BReal sumValue = 0;
	int count = member.vertices->size();
	for (int i = 0; i < count; i++)
	{
		int nx = i + 1;
		if (i == count - 1)
		{
			if (!member.closeLoop)
				break;
			nx = 0;
		}
		BReal length = member.lineLengths[i];
		BReal min = sumValue;
		sumValue += length;
		if (value <= sumValue)
		{
			CornerArc& head = member.cornerArcs[i];
			if (head.vertices.size())
			{
				if (value - min < head.length)
				{
					BReal angle = head.halfAngle - head.halfAngle * (value - min) / head.length;
					BQuater quater(head.axis, -angle);
					BVector vec = head.vertices.last() - head.center;
					return head.center + quater * vec;
				}
			}
			CornerArc& tail = member.cornerArcs[nx];
			if (tail.vertices.size())
			{
				BReal end = sumValue - tail.length;
				if (value > end)
				{
					BReal angle = tail.halfAngle * (value - end) / tail.length;
					BVector vec = tail.vertices.first() - tail.center;
					BQuater quater(tail.axis, angle);
					return tail.center + quater * vec;
				}
			}
			BVector vec = member.vertices->at(nx) - member.vertices->at(i);
			return member.vertices->at(i) + vec * (value - min) / length;
		}
	}
	return member.vertices->first();
}
BVector BLineStripe::directOf(BReal value) const
{
	if (member.vertices.empty())
		return BVector();
	BReal sumValue = 0;
	int count = member.vertices->size();
	for (int i = 0; i < count; i++)
	{
		int nx = i + 1;
		if (i == count - 1)
		{
			if (!member.closeLoop)
				break;
			nx = 0;
		}
		BReal length = member.lineLengths[i];
		BReal min = sumValue;
		sumValue += length;
		if (value <= sumValue)
		{
			BVector direct = member.vertices->at(nx) - member.vertices->at(i);
			direct.normalize();
			CornerArc& head = member.cornerArcs[i];
			if (head.vertices.size())
			{
				if (value - min < head.length)
				{
					BReal angle = head.halfAngle - head.halfAngle * (value - min) / head.length;
					BQuater quater(head.axis, -angle);
					return direct * quater;
				}
			}
			CornerArc& tail = member.cornerArcs[nx];
			if (tail.vertices.size())
			{
				BReal end = sumValue - tail.length;
				if (value > end)
				{
					BReal angle = tail.halfAngle * (value - end) / tail.length;
					BQuater quater(tail.axis, angle);
					return direct * quater;
				}
			}
			return direct;
		}
	}
	return member.vertices->def();
}

BReal BLineStripe::length() const
{
	this->flush();
	return member.length;
}
bool BLineStripe::reverse()
{
	if (member.vertices->size())
	{
		member.vertices->reverse();
		this->dirty();
		return true;
	}
	return false;
}

bool BLineStripe::query(const BString& text, BValue& value) const
{
	if (text == "vertices")
	{
		value = member.vertices;
		return true;
	}
	if (text == "all-points")
	{
		BVectorArray& vertices = value.fill<BVectorArray>();
		vertices.reset();
		for (int i = 0; i < member.cornerArcs.size(); i++)
		{
			CornerArc& corner = member.cornerArcs[i];
			if (corner.vertices.size())
			{
				vertices.append(corner.vertices);
			}
			else
			{
				vertices.append(member.vertices->at(i));
			}
		}
		return true;
	}
	return BShape::query(text, value);
}

BVector BLineStripe::vertex(const BVector& direct) const
{
	return BVector();
}
BVector BLineStripe::inertia(BReal mass) const
{
	return BVector();
}

void BLineStripe::refresh(BBox& box)
{
	member.length = 0;
	BSpace space;
	if (member.vertices.empty())
	{
		member.lineLengths.clear();
		member.cornerArcs.clear();
	}
	else
	{
		space = member.vertices->first();
		member.cornerArcs.resize(member.vertices->size());
		member.lineLengths.resize(member.vertices->size());
		for (int i = 0; i < member.vertices->size() - 1; i++)
		{
			const BVector& v0 = member.vertices->at(i);
			const BVector& v1 = member.vertices->at(i + 1);
			space.expand(v1);
			BReal length = (v0 - v1).length();
			member.lineLengths[i] = length;
			member.length += length;
		}

		if (member.closeLoop)
		{
			const BVector& v0 = member.vertices->last();
			const BVector& v1 = member.vertices->first();
			BReal length = (v0 - v1).length();
			member.lineLengths.last() = length;
			member.length += length;
		}
		else
		{
			member.lineLengths.last() = 0;
		}

		int count = member.vertices->size() - 1;
		for (int i = 1; i < count; i++)
		{
			CornerArc& corner = member.cornerArcs[i];
			const BVector& prev = member.vertices->at(i - 1);
			const BVector& vertex = member.vertices->at(i);
			const BVector& next = member.vertices->at(i + 1);
			member.freshCorner(corner, vertex, prev, next);
		}

		if (member.closeLoop)
		{
			CornerArc& corner = member.cornerArcs.first();
			const BVector& prev = member.vertices->last();
			const BVector& vertex = member.vertices->first();
			const BVector& next = member.vertices->at(1);
			member.freshCorner(corner, vertex, prev, next);
		}
		else
		{
			member.cornerArcs.first().clear();
		}

		if (member.closeLoop)
		{
			CornerArc& corner = member.cornerArcs.last();
			const BVector& prev = member.vertices->at(count - 1);
			const BVector& vertex = member.vertices->last();
			const BVector& next = member.vertices->first();
			member.freshCorner(corner, vertex, prev, next);
		}
		else
		{
			member.cornerArcs.last().clear();
		}
	}
	box = space;
	box.lena() += member.thickness;
	box.lenb() += member.thickness;
	box.lenc() += member.thickness;
}
void BLineStripe::render(BRender& render)
{
	if (member.closeLoop)
		glBegin(GL_LINE_LOOP);
	else
		glBegin(GL_LINE_STRIP);
	for (int i = 0; i < member.cornerArcs.size(); i++)
	{
		CornerArc& corner = member.cornerArcs[i];
		if (corner.vertices.size())
		{
			for (int j = 0; j < corner.vertices.size(); j++)
			{
				const BVector& vertex = corner.vertices[j];
				BOpenGL::glVertex(vertex.x(), vertex.y(), vertex.z());
			}
		}
		else
		{
			const BVector& vertex = member.vertices->at(i);
			BOpenGL::glVertex(vertex.x(), vertex.y(), vertex.z());
		}
	}
	glEnd();
	
	if (member.cornerVisible)
	{
		render.setPointSize(6);
		render.setPointSmooth(true);
		render.setLineWidth(1);

		for (int i = 0; i < member.cornerArcs.size(); i++)
		{
			CornerArc& corner = member.cornerArcs[i];
			if (corner.vertices.empty())
				continue;
			render.setColor(100, 200, 255);
			render.drawVertex(corner.center);
			render.drawLine(corner.center, corner.vertices.first());
			render.drawLine(corner.center, corner.vertices.last());
			render.setColor(200, 0, 0);
			render.drawVertices(corner.vertices);
		}


		render.setColor(200, 200, 200);
		if (member.closeLoop)
			render.drawLineLoop(member.vertices.ref());
		else
			render.drawLineStrip(member.vertices.ref());
	}
}
